/** Commandparser library functions.
 *
 * This file contains functions to ease parsing commands from serial string
 * information.
*/
//##############################################################################




/** Type definitions */
#include "LR_commandparser.h"
//##############################################################################




/** Indicators to determine the mode of the commandparser */
#define MODE_CMD 1
#define MODE_STP 0
#define MODE_PAR 2
//##############################################################################




/** Initialisation of a command parser */
void LR_commandparser_init( LR_commandparser * p,
   LR_commandparser_command * c, unsigned int c_size,
   signed char * b, unsigned int b_size )
{
  p->cmds = c;
  p->cSize = c_size;
  p->buffer = b;
  p->bSize = b_size;
  p->bPos = 0;
  p->bRead = 0;
  p->cPos = 0;
  p->mode = MODE_CMD;
  p->buffer[p->bSize - 1] = '\0';
}
//------------------------------------------------------------------------------




/** Stream a character into the commandparser */
signed char LR_commandparser_stream( LR_commandparser * p, signed char c )
{
  // Used as return value to indicate whether a command string has been finished
  // and a command is (eventually) ready.
  signed char ready = 0;
  // Used to iterate over the command array
  unsigned int iterator;

  // When in CMD mode treat new characters as part of the command word. Only
  // look for special characters, which indicate a mode change.
  if (p->mode == MODE_CMD)
  {
    // Those characters are most likely to appear as a stop character at the end
    // of a command string.
    if (c < ' ')
    {
      p->mode = MODE_STP;
      ready = 1;
    }
    // An opening bracket indicates the start of parameter mode.
    else if (c == '(')
    {
      p->mode = MODE_PAR;
    }
    // All other characters are command word characters
    else
    {
      // Iterator over the command array and look for commands that are still
      // possible.
      for (iterator = 0; iterator < p->cSize; iterator++)
      {
        // Only look at the command word if all previous characters matched.
        if (p->cmds[iterator].state)
        {
          // When the characters are different, signal that the command is
          // definitely not the searched one. Index can not run out of bounds,
          // because then we do not even come to this point due to the
          // previous if.
          if (p->cmds[iterator].word[p->cPos] != c)
          {
            // This way the command will be ignored on next call.
            p->cmds[iterator].state = 0;
          }
        }
      }
      // On next call, look at the next character in the command strings.
      p->cPos++;
    }
  }
  // When in parameter mode, treat every character except stop characters,
  // closing bracket and comma as parameter character.
  else if (p->mode == MODE_PAR)
  {
    // If there is still space in the parameter buffer, then accept the new
    // character from the stream.
    if ((p->bPos + 1) < p->bSize)
    {
      // A comma indicates the start of a new parameter, so finish the current
      // one with a \0 character.
      if (c == ',')
      {
        p->buffer[p->bPos] = '\0';
      }
      // When a stop character or a closing bracket is received, terminate the
      // current parameter with a \0 character, go to stop mode and signal to
      // the caller.
      else if ((c < ' ') || (c == ')'))
      {
        p->buffer[p->bPos] = '\0';
        p->mode = MODE_STP;
        ready = 1;
      }
      // All other characters are parameter characters, so save them into the
      // buffer.
      else
      {
        p->buffer[p->bPos] = c;
      }
      // Iterate to the next buffer position.
      p->bPos++;
    }
    // When there is no more space for parameter characters, ignore all
    // following characters but look for stop characters or a closing bracket.
    // This implies, that parameters will be truncated if the buffer isn't big
    // enough.
    else
    {
      // A stop character or a closing bracket, so signal that the parser is
      // ready.
      if ((c < ' ') || (c == ')'))
      {
        p->mode = MODE_STP;
        ready = 1;
      }
    }
  }
  // When in stop mode, ignore every character and signalize that a command is
  // ready. The caller must then reset the command parser, otherwise this mode
  // will never be left.
  else // if (p->mode == MODE_STP)
  {
    ready = 1;
  }
  // Give signal to the caller.
  return ready;
}
//------------------------------------------------------------------------------




/** Get the parsed command */
signed char LR_commandparser_get( LR_commandparser * p,
    unsigned int * cmd )
{
  // Used to iterate over the command array.
  unsigned int iterator;
  // Used to signal whether the command is valid, i.e. it is the only remaining
  // possible command.
  signed char valid = 0;

  // Search for a remaining possible command. If found, break.
  for (iterator = 0; iterator < p->cSize; iterator++)
  {
    if (p->cmds[iterator].state)
    {
      valid = 1;
      *cmd = p->cmds[iterator].id;
      break;
    }
  }
  iterator++;
  // Search rest of array (if possible) for another remaining possible command.
  // If found, signal that the command is invalid.
  for (; iterator < p->cSize; iterator++)
  {
    if (p->cmds[iterator].state)
    {
      valid = 0;
      break;
    }
  }
  return valid;
}
//------------------------------------------------------------------------------




/** Get one parameter, beginning with the first */
signed char LR_commandparser_parameter( LR_commandparser * p,
    signed char * * parm )
{
  signed char valid = 0;

  if (p->mode == MODE_STP)
  {
    if (p->bRead < p->bPos)
    {
      *parm = &(p->buffer[p->bRead]);
      valid = 1;
      while (p->buffer[p->bRead] != '\0')
      {
        p->bRead++;
      }
      p->bRead++;
    }
  }
  return valid;
}
//------------------------------------------------------------------------------




/** Reset the commandparser into a state where it can start streaming again */
void LR_commandparser_reset( LR_commandparser * p )
{
  p->cPos = 0;
  p->bPos = 0;
  p->bRead = 0;
  p->mode = MODE_CMD;
}
//##############################################################################




